import numpy as np
import config
import cv2
from processor import DataProcessor

class HandwrittenCharacterRecognizer:
    """ANN-based HandwrittenCharacterRecognizer"""

    def __init__(self):
        self.__alphabets = config.alphabets
        self.__classes = {}
        for i in range(len(self.__alphabets)):
            self.__classes[self.__alphabets[i]] = i
        layers = np.array(config.layersList)
        self.__numFeatures = layers[0]
        self.__numClasses = layers[-1]
        criteria = (config.condition, config.nsteps, config.max_err)
        self.__params = dict(term_crit = criteria, 
                    train_method = cv2.ANN_MLP_TRAIN_PARAMS_BACKPROP, 
                    bp_dw_scale = config.step_size, 
                    bp_moment_scale = config.momentum)
        self.__recognizer = cv2.ANN_MLP(layers)
        self.__imageProcessor = DataProcessor()

    def splitDataIntoFeaturesAndClasses(self, data, classIdx):
        """Splitsthe data-table into a feature-table and a class-table while maintaining the order."""

        featuresList = []
        classList = []
        for entry in data:
            featuresList.append(entry[:classIdx] + entry[(classIdx + 1):])
            classList.append(entry[classIdx])
        return featuresList, classList

    def __getInputArray(self, featuresList):
        """Returns a numpy.arry form of the list of features (one for each object)."""

        dataSize = len(featuresList)
        inputs = np.empty((dataSize, self.__numFeatures), 'float')
        for i in range(dataSize):
            a = np.array(featuresList[i])
            inputs[i, :] = a[:]
        return inputs

    def __getTargetsArray(self, classList):
        """Converts a list of classes to a binary numpy.array.
        In each row, '1' indicates the class of the object."""

        dataSize = len(classList)
        targets = -1 * np.ones((dataSize, self.__numClasses), 'float')
        for i in range(dataSize):
            targets[i, self.__classes[classList[i]]] = 1
        return targets

    def train(self, trainingData, classIdx):
        """Train the prediction model."""

        featuresList, classList = self.splitDataIntoFeaturesAndClasses(trainingData, classIdx)
        inputs = self.__getInputArray(featuresList)
        targets = self.__getTargetsArray(classList)
        numIter = self.__recognizer.train(inputs, targets, None, params=self.__params)
        return numIter

    def predictUsingDataBatch(self, data):
        """Returns a list containing the predicted alphabet for each feature vector (one for each image)
        in the passed data."""

        testingDataSize = len(data)
        inputs = self.__getInputArray(data)
        predictions = np.empty((testingDataSize, self.__numClasses), 'float')
        self.__recognizer.predict(inputs, predictions)
        predictedClasses = list(np.argmax(predictions, axis=1))
        predictedLabels = [self.__alphabets[p] for p in predictedClasses]
        return predictedLabels

    def recognizeImage(self, imagePath):
        """Predicts the alphabet the image file might be representing."""

        features = self.__imageProcessor.extractFeaturesFromImageFile(imagePath)
        predictedLabel = self.predictUsingDataBatch([features])[0]
        return predictedLabel
